#! /usr/bin/env python

import os

from databases.db2 import Db2
from databases.mssql import Mssql
from databases.mysql import Mysql
from databases.oracle import Oracle
from databases.postgres import Postgres
from databases.sqlite import Sqlite

from lib.add_column import AddColumn
from lib.create_table import CreateTable
from lib.drop_table import DropTable
from lib.rename_table import RenameTable
from lib.rename_column import RenameColumn
from lib.remove_column import RemoveColumn
from lib.change_column import ChangeColumn
from lib.add_index import AddIndex
from lib.remove_index import RemoveIndex
from lib.sql import Sql
from lib.code import Code

#Config stuff here
config = {"development" :
            {"db" : "mysql://root:@localhost/tgroups_twitter_development",
             "migration_path" : "./migrations/"},
         "qa" :
            {"db" : {"db" : ""},
             "migration_path" : "./migrations/"},
         "production" :
            {"db" : {"db" : ""},
             "migration_path" : "./migrations/"}}



class PyMigrate():
    class DuplicateMigrationException(Exception):
        def __init__(self, msg):
            Exception.__init__(self, msg)

    class MigrationExecutionException(Exception):
        def __init__(self, msg):
            Exception.__init__(self, msg)

    config = None
    commands_list = ['add_column',
                    'remove_column',
                    'change_column',
                    'rename_column',
                    'sql',
                    'create_table',
                    'drop_table',
                    'rename_table',
                    'remove_index',
                    'add_index',
                    'code']
    db = None

    def __init__(self, config):
        #Tested
        self.config = config

    def run(self, opt_version, environment):
        error = False
        self.db = eval(self._driver(environment).capitalize())(self.config[environment]['db'])
        version = self.db.get_version()

        try:
            files_list = self._build_files_list(environment)
        except Exception, e:
            print e
            error = True
            
        if not error and (not opt_version == None and int(version) == int(opt_version)) or \
            (opt_version == None and len(files_list) > 0  and int(files_list[len(files_list) - 1]['number']) == version):
            print "Database schema already at version: %s" % (opt_version or files_list[len(files_list) - 1]['number'])
            error = True

        if not error:
            control = self._determine_stop_version(version, opt_version, files_list)

            try:
                self._run_files(files_list, control, self.db, environment)
            except Exception, e:
                print e
                error = True

        version_at_stop = self.db.get_version()
        self.db.close()
        return version_at_stop

    def _driver(self, environment):
        end = self.config[environment]['db'].find(":")
        return self.config[environment]['db'][:end]

    def _get_number(self, filename):
        #Tested
        end = filename.find("_")
        return filename[:end]

    def _verify_unique(self, filename, files):
        #Tested
        number = self._get_number(filename)
        count = 0
        for new_file in files:
            if new_file.startswith(number):
                    count = count + 1
            if count > 1:
                raise self.DuplicateMigrationException("more than one files \
                    starting with %s" % number)

        return True

    def _build_files_list(self, environment):
        #Tested
        files_list = []
        for root, dirs, files in \
            os.walk(self.config[environment]['migration_path']):
            files.sort()
            for file in files:
                #Only trying to find fields we can use. Subversion has the .svn which we need to throw out
                try:
                    int(self._get_number(file))
                except:
                    break

                #find any duplicate numbers
                try:
                    self._verify_unique(file, files)
                except self.DuplicateMigrationException, e:
                    raise e
                except:
                    raise "Unknown Error"

                #load all the files into the files list array for further processing
                files_list.append({"number" : self._get_number(file),
                                   "file" : file})

        return files_list

    def _print_file_info(self, file):
        print ""
        print "Loading file %s:" % file
        print "================================================"

    def _parse_command(self, line):
        #Tested
        command_array = line.find(":")
        return line[:command_array].strip(), line[command_array + 1:].strip()

    def _start_file_commands_recording(self, start, line):
        found = False
        if line.lower().strip() == start:
            found = True
        return found

    def _stop_file_commands_recording(self, end, line):
        found = False
        if line.lower().strip() == end:
            found = True
        return found

    def _build_queries_list(self, queries, line):
        temp_line = line.strip()
        code_line = line
        head, tail = self._parse_command(temp_line)

        #Here there be dragons. TODO: Must slay them
        if head in self.commands_list:
            queries.append({'command' : head, "data" : tail})
        elif len(queries) - 1 >= 0 and not temp_line == "" and not temp_line.startswith("#") and not (queries[len(queries) - 1]['command'].endswith('code') or queries[len(queries) - 1]['command'].endswith('sql')):
            queries[len(queries) - 1]['data'] = "%s, %s" % (queries[len(queries) - 1]['data'], temp_line)
        elif len(queries) - 1 >= 0 and not temp_line == "" and not temp_line.startswith("#") and (queries[len(queries) - 1]['command'].endswith('code') or queries[len(queries) - 1]['command'].endswith('sql')):
            queries[len(queries) - 1]['data'] = "%s%s" % (queries[len(queries) - 1]['data'], code_line)
        elif (head.endswith('code') or head.endswith('sql')) and len(queries) - 1 < 0:
            queries.append({'command' : head, "data" : tail})
    
    def _parse_file(self, start, end, file, environment):
        #Tested
        doc = open("%s%s" %  (self.config[environment]['migration_path'], file))
        record = False
        queries = []
        for line in doc.readlines():
            if self._start_file_commands_recording(start, line):
                record = True
            elif self._stop_file_commands_recording(end, line):
                record = False
                
            if record == True:
                self._build_queries_list(queries, line)

        return queries

    def _determine_stop_version(self, version, opt_version, files_list):
        #Tested
        control = {"start" : version, "stop" : "", "walk" : "up"}
        if opt_version == None:
            control['stop'] = int(files_list[len(files_list) - 1]['number'])
        elif not opt_version == None:
            for file in files_list:
                if int(file['number']) <= int(opt_version):
                    control['stop'] = int(file['number'])

            if version > int(opt_version):
                control['walk'] = "down"
                if control['stop'] == '':
                    control['stop'] = int(opt_version)
                    
        if control['walk'] == 'down':
            files_list.reverse()
            
        return control

    def _run_files(self, files_list, control, db, environment):
        #Tested
        for i in range(len(files_list)):
            try:
                if self._run_version_up(control, i, files_list):
                    self._execute_file("up", "down", i,
                        files_list, db, files_list[i]['number'], environment)

                elif self._run_version_down(control, i, files_list) \
                     and int(control['stop']) >= \
                     int(files_list[len(files_list) - 1]['number']):
                    self._execute_file("down", "up", i, files_list, db,
                        files_list[i + 1]['number'], environment)

                elif self._run_version_down(control, i, files_list) \
                    and int(control['stop']) < \
                    int(files_list[len(files_list) - 1]['number']):

                    self._execute_file("down", "up", i, files_list, db,
                        control['stop'], environment)
            except Exception, e:
                raise self.MigrationExecutionException("\nAn error has occured \
                    in file: %s. Please check the format.\n" \
                    % files_list[i]['file'])

    def _run_version_up(self, control, i, files_list):
        #Tested
        if control['walk'] == 'up' \
                    and control['start'] <  int(files_list[i]['number']) \
                    and control['stop'] >= int(files_list[i]['number']):
            return True
        return False

    def _run_version_down(self, control, i, files_list):
        #Tested
        if control['walk'] == 'down' \
                    and control['start'] >=  int(files_list[i]['number']) \
                    and control['stop'] < int(files_list[i]['number']):
            return True
        return False

    def _load_executables(self, command, executables, db):
        tmp = ""
        if "_" in command['command']:
            tmp = command['command'].split("_")
            tmp = "%s%s" % (tmp[0].capitalize(), tmp[1].capitalize())
        else:
            tmp = "%s" % (command['command'].capitalize())

        executables.append(eval(tmp)(db, command['data']))

    def _execute_file(self, up, down, i, files_list, db, version, environment):
        #Tested
        self._print_file_info(files_list[i]['file'])
        commands = self._parse_file("[%s]" % up, "[%s]" % down, files_list[i]['file'], environment)
        executables = []
        for command in commands:
            self._load_executables(command, executables, db)

        for executable in executables:
            try:
                executable.execute()
                print ""
            except:
                if up == "up":
                    old_version = db.get_version()
                    db.update_schema(old_version)
                else:
                    db.update_schema(files_list[i]['number'])
                raise self.MigrationExecutionException("database fail")

        if int(version) == 0 and len(files_list) - 1 == i:
            db.update_schema(version)
        else:
            if up == "up":
                db.update_schema(files_list[i]['number'])
            else:
                db.update_schema(int(files_list[i]['number']) - 1)
        
if __name__ in "__main__":
    from optparse import OptionParser
    p = OptionParser()
    p.add_option('-v', '--version', dest='version', default=None,
                 help="version to restore the schema info migration to")

    p.add_option('-e', '--environment', dest='environment', default="development",
                 help="Which config setting to run")
                 
    options, args = p.parse_args()

    pm = PyMigrate(config)
    pm.run(options.version, options.environment)